-module(flv).
-author(hijole).
-compile(export_all).

-define(PATH, "c:\\PROJECTS\\Downloads\\val.flv").
-define(BEAT, "c:\\PROJECTS\\Downloads\\beat.flv").
-define(AUDIODATA, 8).
-define(VIDEODATA, 9).
-define(SCRIPTDATAOBJECT, 18).


valid(_Bin) ->
    true.

-record(flv_tag, {previous, type, datasize, timestamp, data}).
-record(flv_info, {version = 1, header_size = 9, has_audio = 1, has_video = 1, body}).

parse(Bin) ->
    %% Bin is whole file content as a binary 
    <<"FLV", Version:8, 0:5, HasAudio:1, 0:1, HasVideo:1, HeaderSize:32, _NotHeader/binary>> = Bin,
    <<_Header:HeaderSize/binary, Body/binary>> = Bin,
    #flv_info{version = Version,
	      header_size = HeaderSize,
	      has_audio = HasAudio,
	      has_video = HasVideo,
	      body = Body}.

timestamp(Upper, Lower) ->
    <<Timestamp:32/integer-big>> = <<Upper:8, Lower: 24>>,
    Timestamp.

tag(Bin) ->
    %% Bin is body of flv as returned by flv:parse
    <<Prev:32/integer, Type:8/integer, DataSize:24/integer, Timestamp:24/integer-big,
     UpperTimestamp:8/integer-big, _Nulls:3/binary, DataStart/binary>> = Bin,
    {Data, Rest} = split_binary(DataStart, DataSize),
    {#flv_tag{previous = Prev, type = Type,
	      datasize = DataSize, timestamp = timestamp(UpperTimestamp, Timestamp),
	      data = Data},
     Rest}.

prev_size(Bin) ->
    %% Bin starts with tag
    <<PrevSize:32/integer-big, _Rest/binary>> = Bin,
    PrevSize.

foldl_tags(Fun, Value, Bin) ->
    %% well, foldl ...
    %% don't forget to rewrite each_tag and map_tags with this
    %% and Bin argument in this function really must be record flv_tag
    case tag(Bin) of
	{Tag, <<_EndSize:32/integer>>} ->
	    Fun(Value, Tag);
	{Tag, Next} ->
	    foldl_tags(Fun, Fun(Value, Tag), Next)
    end.

map_tags(Fun, Bin) ->
    %% Bin is body of flv as returned by flv:parse
    lists:reverse(foldl_tags(fun(List, Tag) -> [Fun(Tag) | List] end,
			     [], Bin)).

each_tag(Fun, Bin) ->
    %% Bin is body of flv as returned by flv:parse
    foldl_tags(fun(_, Tag) -> Fun(Tag) end,
	       undefined, Bin).

duration(Flv) ->
    %% Flv is result of info
    foldl_tags(fun(_Value, Tag) -> Tag#flv_tag.timestamp end, 0, Flv#flv_info.body).

with_metadata(Fun, Info) ->
    %% Info is result of info(Path)
    foldl_tags(fun(Value, Tag) -> case Tag#flv_tag.type of
				      ?SCRIPTDATAOBJECT -> Fun(Tag);
				      _ -> Value
				  end
	       end, 0, Info#flv_info.body).

mapfilter(Fun, Pred, Info) ->
    foldl(fun(Result, Tag) ->
		  case Pred(Tag) of
		      true ->
			  [Tag | Result];
		      _ -> Result
		  end
	  end, []).

map(Fun, Info) ->
    map_tags(Fun, Info#flv_info.body).

foldl(Fun, Value, Info) ->
    foldl_tags(Fun, Value, Info#flv_info.body).

each(Fun, Info) ->
    each_tag(Fun, Info#flv_info.body).

timestamp(Tag) ->
    %% tuple {Upper, Lower}
    <<Upper:8/integer, Lower:24/integer-big>> =
	<<(Tag#flv_tag.timestamp):32/integer-big>>,
    {Upper, Lower}.

filter(Pred, Info) ->
    %% tags in Info such that Pred(Tag) -> true (sizes of previous tags are setted automatically)
    foldl(fun({Previous, Result}, Tag) ->
				case Pred(Tag) of
				    true ->
					{Upper, Lower} = timestamp(Tag),
					{Tag,
					 Result#flv_info{body = <<(Result#flv_info.body)/binary,
								 (Previous#flv_tag.datasize + 11):32/integer-big,
								 (Tag#flv_tag.type):8/integer-big,
								 (Tag#flv_tag.datasize):24/integer-big,
								 Lower:24/integer-big,
								 Upper:8/integer-big,
								 0:24,
								 (Tag#flv_tag.data)/binary>>}};
				    false -> {Previous, Result}
				end
			end, {#flv_tag{previous = 0,
				       timestamp = 0,
				       datasize = -11},
			      #flv_info{version = Info#flv_info.version,
					header_size = Info#flv_info.header_size,
					has_audio = Info#flv_info.has_audio,
					has_video = Info#flv_info.has_video,
					body = <<>>}},
			Info).

only_video(Info) ->
    %% extract only video tags from FLV
    {_, Result} = filter(fun(Tag) ->
		   Tag#flv_tag.type =:= ?VIDEODATA
	   end, Info),
    Result.

only_audio(Info) ->
    %% extract only video tags from FLV
    {_, Result} = filter(fun(Tag) ->
		   Tag#flv_tag.type =:= ?AUDIODATA
	   end, Info),
    Result.

save(#flv_info{version = Version, has_audio = HasAudio,
	       has_video = HasVideo, header_size = HeaderSize, body = Body},
     OutPath) ->
    {ok, OutStream} = file:open(OutPath, write),
    file:write(OutStream, <<"FLV", Version:8, 0:5, HasAudio:1, 0:1, HasVideo:1, HeaderSize:32/big>>),
						% duplication here!
    file:write(OutStream, <<0:(HeaderSize - 9)>>),
    file:write(OutStream, Body),
    file:close(OutStream).



copy(In, Out) ->
    %% copy In file to Out
    {ok, Bin} = file:read_file(In),
    file:write_file(Out, Bin).

merge(_Timestamp, Previous, [], OutStream) ->
    file:write(OutStream, <<Previous:32/big>>),
    file:close(OutStream);
merge(Timestamp, Previous, [Path | Paths], OutStream) ->
    #flv_info{body = Body} = info(Path),
    Timestamps = map_tags(fun(T) when T#flv_tag.type =/= ?SCRIPTDATAOBJECT ->
				  %% io:format("~p~n", [T]),
				  <<Upper:8/integer, Lower:24/integer-big>> =
				      <<(T#flv_tag.timestamp + Timestamp):32/integer-big>>,
				  file:write(OutStream, <<Previous:32/integer-big,
							 (T#flv_tag.type):8/integer,
							 (T#flv_tag.datasize):24/integer-big,
							 Lower:24/integer-big, Upper:8/integer,
							 0:24, (T#flv_tag.data)/binary>>),
				  {T#flv_tag.timestamp, T#flv_tag.previous};
			     (_) -> {0, 0}
			  end, Body),
    {LastTimestamp, LastPrevious} = lists:last(Timestamps),
    merge(Timestamp + LastTimestamp, LastPrevious, Paths, OutStream).

merge(Paths, OutPath) ->
    {ok, OutStream} = file:open(OutPath, write),
    file:write(OutStream, <<"FLV", 1:8, 0:5, 1:1, 0:1, 1:1, 9:32/big>>),
    merge(0, 0, Paths, OutStream).

info(Path) ->
    %% flv_info record for Path
    {ok, Bin} = file:read_file(Path),
    parse(Bin).

test_merge() ->
    merge([?PATH, ?BEAT], "out.flv").
